package com.zch.starter.sentinel.springboot.starter.config;

import com.alibaba.csp.sentinel.slots.block.BlockException;
import lombok.extern.slf4j.Slf4j;

import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
import org.springframework.core.ParameterNameDiscoverer;
import org.springframework.core.StandardReflectionParameterNameDiscoverer;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestControllerAdvice;

import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.stream.IntStream;

/**
 * @RestControllerAdvice 注解 主动拦截controller 层异常并返回自定义结果
 */
@Slf4j
@RestControllerAdvice
public class GlobalSentinelBlockExceptionHandler {
    private static final ParameterNameDiscoverer parameterNameDiscoverer = new StandardReflectionParameterNameDiscoverer();

    @ExceptionHandler(BlockException.class)
    @ResponseBody
    public SentinelResult<Object> handleBlockException(BlockException ex) {
        return SentinelResult.rejectPass();
    }



    // 适合主动编码 捕捉到限流异常后主动调用 simpleHandleFlowBlock方法
    public static SentinelResult simpleHandleFlowBlock( BlockException ex, Object...args ) {
        log.warn("触发限流 | 参数: {}",
                formatArgs(args));
        return SentinelResult.rejectPass();

    }
    /**
     * 安全格式化参数数组（高性能实现）
     */
    private static String formatArgs(Object[] args) {
        if (ArrayUtils.isEmpty(args)) {
            return "[]";
        }

        StringBuilder sb = new StringBuilder("[");
        for (int i = 0; i < args.length; i++) {
            if (i > 0) sb.append(", ");

            Object arg = args[i];
            if (arg == null) {
                sb.append("null");
            } else {
                sb.append(ToStringBuilder.reflectionToString(arg,
                        ToStringStyle.SHORT_PREFIX_STYLE)); // 使用Apache Commons Lang
            }
        }
        sb.append("]");
        return sb.toString();
    }
    // 这里面用到了反射 性能低，所以废弃了，建议主动使用时候使用simpleHandleFlowBlock 方法
    @Deprecated
    public static SentinelResult handleFlowBlock(BlockException ex, Object...args) {
        // 1. 获取调用方法的参数信息
        String methodInfo = getMethodInfo();
        Map<String, Object> paramMap = buildParamMap(args);

        // 2. 打印完整参数日志
        log.warn("触发限流 | 方法: {} | 参数: {} ",
                methodInfo,
                paramMap);

        return SentinelResult.rejectPass();

    }
    private static Map<String, Object> buildParamMap( Object[] args) {
        Map<String, Object> paramMap = new HashMap<>();
        try {
            // 通过堆栈获取调用方法
            StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
            String className = stackTrace[3].getClassName();
            String methodName = stackTrace[3].getMethodName();

            Class<?> clazz = Class.forName(className);
            Method method = findMethod(clazz, methodName, args);

            // 获取参数名
            String[] paramNames = parameterNameDiscoverer.getParameterNames(method);
            if (paramNames != null) {
                IntStream.range(0, Math.min(paramNames.length, args.length))
                        .forEach(i -> paramMap.put(paramNames[i], args[i]));
            }
        } catch (Exception e) {
            log.debug("获取参数名失败", e);
            // 降级方案：用arg0,arg1...作为key
            IntStream.range(0, args.length)
                    .forEach(i -> paramMap.put("arg" + i, args[i]));
        }
        return paramMap;
    }

    private static Method findMethod(Class<?> clazz, String methodName, Object[] args) {
        return Arrays.stream(clazz.getDeclaredMethods())
                .filter(m -> m.getName().equals(methodName))
                .filter(m -> m.getParameterCount() == args.length)
                .findFirst()
                .orElseThrow(() -> new IllegalArgumentException("方法未找到"));
    }

    private static String getMethodInfo() {
        StackTraceElement element = Thread.currentThread().getStackTrace()[3];
        return element.getClassName() + "#" + element.getMethodName();
    }
}
